iT邦幫忙

2023 iThome 鐵人賽

DAY 20
0
Modern Web

30 天淺入淺出 Next.js 13系列 第 20

Day 20 - Intercepting Routes 誰說路由導航要整個跳過去的

  • 分享至 

  • xImage
  •  

前言

上面的 gif 演示了這個圖床網站的運作方式。

當我們點擊某一張圖片時可以看到一個 modal 彈出來,顯示我們點擊的圖片。此時可以注意到網址列已經改變,當重新整理時會看到網頁的結果跟一開始點擊時不一樣,它不是以 modal 的方式顯示,而且它已經脫離首頁,是單頁的圖片介紹。

這就是 intercepting routes 常見的使用方式。

當在網站內作路由導航時,可以攔截這個導航,讓我們在不脫離原本所在頁面的情況下,顯示另一個頁面的部分資訊。

當我們直接訪問這個網址(硬導航、重新整理或訪問這個網址),那就不會被攔截,而是直接顯示該網址原本應該顯示的內容。

大綱

  • Intercepting routes 介紹
  • 使用方式
  • route.back()

Intercepting routes 介紹

intercepting routes 是基於軟導航(soft navigation)的應用,每個軟導航都會通過 Next 建立的 middleware。

這個 middleware 其中一個作用就是查看該網址需不需要攔截下來,需要的話網址一樣會更改,但不是直接訪問到該頁面,而是吐出我們另外寫的,對應該頁面的 UI。

通常都會是 Modal 彈窗,否則該 UI 會基於原頁面直接往下長,看起來會蠻奇怪的。

使用方式

建立 intercepting routes 需要建立 (..) 系列的保留字建立檔案,這裡的 .. 跟相對路徑的概念很像,一個點代表要 match 同一個 level 的對應路由,兩個點代表要配對上一層 level 的對應路由。

括號點的規則:

  • (.):match 同一層 route
  • (..):match 上一層 route
  • (..)(..):match 上兩層 route
  • (...):match 根目錄 route

我們建立的 intercepting route 需要對應一個真實存在的路由

以上面來說建立 app/feed/(..)photo/[id]/page.tsx 這個 intercepting route 以前,應該要先有 app/photo/[id]/page.tsx 這個真實存在的路由。

當建立好這個 intercepting route 以後,只要是軟導航到 /photo/[id],就會吐出我們建立的 app/feed/(..)photo/[id]/page.tsx

而硬導航、重新整理或直接訪問 /photo/[id] 則會直接顯示原頁面的 UI (app/photo/[id]/page.tsx)。

這裡講的 match route 以資料夾來看,只會算進對 route 產生影響的資料夾。
像是前幾天講到的 route group (groupName) 或是 parallel routes @folder,這類不會對實際對路由造成影響的資料夾,不會算作一層

route.back()

開頭的圖床網站還有一個功能沒有講到,圖片 modal 彈出來後,可以點擊 modal 周圍的 overlay 或 eac,路由會退回原本訪問的頁面(modal 也會因為路由退回去而消失)。

這就需要使用 useRouter 裡提供的 back method 回到上一頁,這是一個 react hook,所以只能在 client component 中使用。

底下是一個官方的 Modal 組件範例,這裡的 onDissmiss 使用 Next 提供的 useRouter 作出回到原頁面的動作,按下 esc 或是點擊 modal 周圍的 overlay 時會觸發。

// client component
'use client'
import { useCallback, useRef, useEffect, MouseEventHandler } from 'react'
import { useRouter } from 'next/navigation'

export default function Modal({ children }: { children: React.ReactNode }) {
  const overlay = useRef(null)
  const wrapper = useRef(null)
  const router = useRouter()

  const onDismiss = useCallback(() => {
    router.back()
  }, [router])

  const onClick: MouseEventHandler = useCallback(
    (e) => {
      if (e.target === overlay.current || e.target === wrapper.current) {
        if (onDismiss) onDismiss()
      }
    },
    [onDismiss, overlay, wrapper]
  )

  const onKeyDown = useCallback(
    (e: KeyboardEvent) => {
      if (e.key === 'Escape') onDismiss()
    },
    [onDismiss]
  )

  useEffect(() => {
    document.addEventListener('keydown', onKeyDown)
    return () => document.removeEventListener('keydown', onKeyDown)
  }, [onKeyDown])

  return (
    <div
      ref={overlay}
      className="fixed z-10 left-0 right-0 top-0 bottom-0 mx-auto bg-black/60"
      onClick={onClick}
    >
      <div
        ref={wrapper}
        className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-full sm:w-10/12 md:w-8/12 lg:w-1/2 p-6"
      >
        {children}
      </div>
    </div>
  )
}

參考資料


上一篇
Day 19 - Parallel Routes 路由的平行宇宙
下一篇
Day 21 - Route Handler 在 Next 建立 API
系列文
30 天淺入淺出 Next.js 1321
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言